package linc.fun.openai.service.impl;

import cn.hutool.core.util.StrUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import linc.fun.openai.config.exchange.ExchangeConfig;
import linc.fun.openai.constants.RedisKeyConstants;
import linc.fun.openai.domain.dto.query.MyExchangesPageQuery;
import linc.fun.openai.domain.entity.chat.ChatExchangeDO;
import linc.fun.openai.domain.entity.chat.ChatUserDO;
import linc.fun.openai.domain.entity.chat.ChatUserExchangeRecordDO;
import linc.fun.openai.enums.EnableDisableStatusEnum;
import linc.fun.openai.exception.BizException;
import linc.fun.openai.mapper.ChatExchangeMapper;
import linc.fun.openai.mapper.ChatUserExchangeRecordMapper;
import linc.fun.openai.mapper.ChatUserMapper;
import linc.fun.openai.service.ChatExchangeService;
import linc.fun.openai.util.ChatUserUtil;
import linc.fun.openai.util.ExchangeCodeUtil;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static linc.fun.openai.domain.entity.chat.table.Tables.ChatExchange;
import static linc.fun.openai.domain.entity.chat.table.Tables.ChatUserExchangeRecord;

/**
 * @author yqlin
 * @date 2023/5/8 00:04
 * @description
 */
@Slf4j
@Service
public class ChatExchangeServiceImpl extends ServiceImpl<ChatExchangeMapper, ChatExchangeDO> implements ChatExchangeService {
    @Resource
    private ChatUserMapper chatUserMapper;
    @Resource
    private ChatUserExchangeRecordMapper chatUserExchangeRecordMapper;
    @Resource
    private Redisson redisson;
    @Resource
    private ExchangeConfig exchangeConfig;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Integer immediateExchangeConversation(String code) {
        // 校验兑换码
        this.checkCodeOk(code);
        // 校验兑换码参数
        ChatExchangeDO exchange = this.checkExchangeOk(code);
        // 增加用户聊天使用次数
        return this.doIncreaseConversationTimes(exchange);
    }

    @Override
    public Page<ChatExchangeDO> pageMyExchanges(MyExchangesPageQuery query) {
        Page<ChatExchangeDO> page = new Page<>(query.getPageNum(), query.getPageSize());
        return this.page(page, QueryWrapper.create()
                .select(ChatExchange.Id,
                        ChatExchange.ProductId,
                        ChatExchange.ActivationTime,
                        ChatExchange.ExpiresDays,
                        ChatExchange.Code,
                        ChatExchange.Name,
                        ChatExchange.Price,
                        ChatExchange.RewardUseTimes,
                        ChatExchange.IsUsed
                ).from(ChatExchange)
                .innerJoin(ChatUserExchangeRecord)
                .on(ChatUserExchangeRecord.ExchangeId.eq(ChatExchange.Id))
                .where(ChatUserExchangeRecord.UserId.eq(ChatUserUtil.getUserId()))
                .and(ChatExchange.Code.eq(query.getCode())
                        .when(StrUtil.isNotBlank(query.getCode()))));
    }

    private void checkCodeOk(String code) {
        boolean ok = ExchangeCodeUtil.verifyCode(code, exchangeConfig.getCodeLength(), exchangeConfig.getSecret());
        if (!ok) {
            throw BizException.INVALID_EXCHANGE_CODE;
        }
    }

    @NotNull
    private ChatExchangeDO checkExchangeOk(String code) {

        // 数据不存在
        ChatUserExchangeRecordDO userExchangeRecord = chatUserExchangeRecordMapper.selectOneByQuery(QueryWrapper.create()
                .where(ChatUserExchangeRecord.Code.eq(code)
                        .and(ChatUserExchangeRecord.UserId.eq(ChatUserUtil.getUserId()))
                )
        );
        if (Objects.isNull(userExchangeRecord)) {
            throw BizException.CURRENT_EXCHANGE_NOT_EXIST;
        }

        ChatExchangeDO exchange = this.getById(userExchangeRecord.getExchangeId());

        // 数据丢失
        if (Objects.isNull(exchange)) {
            throw BizException.DATA_HAS_BEEN_LOOSED;
        }

        // 禁用
        if (!EnableDisableStatusEnum.ENABLE.equals(exchange.getStatus())) {
            throw BizException.CURRENT_EXCHANGE_DISABLED;
        }

        // 已使用
        if (exchange.getIsUsed()) {
            throw BizException.CURRENT_EXCHANGE_IS_USED;
        }

        // 已过期
        LocalDateTime expireTime = exchange.getActivationTime().plus(exchange.getExpiresDays(), ChronoUnit.DAYS);
        if (expireTime.isBefore(LocalDateTime.now())) {
            throw BizException.CURRENT_EXCHANGE_EXPIRED;
        }

        return exchange;
    }

    private Integer doIncreaseConversationTimes(ChatExchangeDO exchange) {
        ChatUserDO user = chatUserMapper.selectOneById(ChatUserUtil.getUserId());
        if (Objects.isNull(user)) {
            throw BizException.CURRENT_USER_NOT_EXIST;
        }
        RLock rLock = redisson.getLock(RedisKeyConstants.CHAT_USER_USAGE_LOCK_KEY);
        try {
            rLock.lock(2, TimeUnit.MINUTES);
            Integer conversationTimes = Objects.isNull(user.getCanConversationTimes()) ? 0 : user.getCanConversationTimes();
            user.setCanConversationTimes(conversationTimes + exchange.getRewardUseTimes());
            user.setUpdateTime(LocalDateTime.now());
            // 将兑换码变为已使用
            exchange.setIsUsed(true);
            exchange.setUpdateTime(LocalDateTime.now());
            // todo: 编程式事务可以使用
            chatUserMapper.update(user);
            this.updateById(exchange);
            return exchange.getRewardUseTimes();
        } catch (Exception e) {
            log.error("兑换聊天次数异常", e);
            throw BizException.CHAT_USER_USAGE_INCREASE_ERROR;
        } finally {
            rLock.unlock();
        }
    }
}
